/*-------------------------------------------------------------------------
 *
 * spell.c
 *		使用ISpell规范化单词
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 *
 * Ispell字典
 * -----------------
 *
 * 字典的规则在两个文件中定义，扩展名为.affix和.dict。它们被拼写检查程序Ispell和Hunspell使用。
 *
 * .affix文件声明形态学规则，以获取单词的基本形式。对于Ispell和Hunspell字典，.affix文件的格式结构不同。Hunspell格式更复杂。但是，当导入和编译.affix文件后，它以相同的结构AffixNode存储。
 *
 * .dict文件存储基本形式的单词列表，并引用附加规则。对于Ispell和Hunspell字典，.dict文件的格式具有相同的结构。
 *
 * 字典的编译
 * ---------------------------
 *
 * 编译后的字典存储在IspellDict结构中。字典的编译分为几个步骤：
 *	- NIImportDictionary() - 将.dict文件的每个单词存储在临时Spell字段中。
 *	- NIImportAffixes() - 如果.affix文件具有Ispell格式，则将.affix文件的附加规则存储在Affix字段（非临时）中。
 *	  -> NIImportOOAffixes() - 如果.affix文件具有Hunspell格式，则存储附加规则。如果定义了AF参数，则初始化AffixData字段。
 *	- NISortDictionary() - 从单词列表构建一个前缀树（Trie），并将其存储在Dictionary字段中。单词列表来自Spell字段。如果未定义AF参数，则初始化AffixData字段。
 *	- NISortAffixes():
 *	  - 从附加列表构建复合附加列表并将其存储在CompoundAffix中。
 *	  - 从附加列表构建复合前缀树（Trie）并将其存储在后缀和前缀字段中。
 *	  附加列表来自Affix字段。
 *
 * 内存管理
 * -----------------
 *
 * IspellDict结构具有仅在编译时使用的Spell字段。Spell字段存储一个单词列表。它可能会占用大量内存。
 * 因此，在编译字典时，此字段由NIFinishBuild()清除。
 *
 * 所有资源在NIFinishBuild()之前应通过tmpalloc()和tmpalloc0()初始化。
 *
 * IDENTIFICATION
 *	  src/backend/tsearch/spell.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "catalog/pg_collation.h"
#include "miscadmin.h"
#include "tsearch/dicts/spell.h"
#include "tsearch/ts_locale.h"
#include "utils/memutils.h"


/*
 * 初始化需要大量的内存，而在初始化完成后这些内存就不再需要。初始化期间，
 * CurrentMemoryContext 是与字典缓存条目关联的长期内存上下文。我们将短期对象
 * 保存在 Conf->buildCxt 上下文中。
 */
#define tmpalloc(sz)  MemoryContextAlloc(fc_Conf->buildCxt, (sz))
#define tmpalloc0(sz)  MemoryContextAllocZero(Conf->buildCxt, (sz))

/*
 * 准备构造一个 ISpell 字典。
 *
 * 假设分配的 IspellDict 结构体已被初始化为零。
 */
void NIStartBuild(IspellDict *fc_Conf)
{
	/*
 * 临时上下文是 CurTransactionContext 的子上下文，因此在出错时它会自动消失。
 */
	fc_Conf->buildCxt = AllocSetContextCreate(CurTransactionContext,
										   "Ispell dictionary init context",
										   ALLOCSET_DEFAULT_SIZES);
}

/*
 * 字典构造完成时清理。
 */
void NIFinishBuild(IspellDict *fc_Conf)
{
	/* 释放不再需要的临时内存 */
	MemoryContextDelete(fc_Conf->buildCxt);
	/* 为了整洁，将现在的悬空指针清零 */
	fc_Conf->buildCxt = NULL;
	fc_Conf->Spell = NULL;
	fc_Conf->firstfree = NULL;
	fc_Conf->CompoundAffixFlags = NULL;
}


/*
 * "紧凑" palloc：分配时没有额外的 palloc 开销。
 *
 * 由于我们不需要单独释放 ispell 数据项，因此在每个块上一般由 palloc 消耗的
 * 开销并没有太大价值。去掉它是有帮助的，因为 ispell 可能会分配很多小节点。
 *
 * 我们目前对以这种方式分配的所有数据进行预先清零，尽管其中一些并不需要这样。
 * cpalloc 和 cpalloc0 宏只是文档，用于指示哪些分配确实需要清零。
 */
#define COMPACT_ALLOC_CHUNK 8192	/* 一次从 palloc 获取的数量 */
#define COMPACT_MAX_REQ		1024	/* 必须小于 COMPACT_ALLOC_CHUNK */

static void * fc_compact_palloc0(IspellDict *fc_Conf, size_t fc_size)
{
	void	   *fc_result;

	/* 只能在初始化期间调用 */
	Assert(fc_Conf->buildCxt != NULL);

	/* 对于大块没有意义 */
	if (fc_size > COMPACT_MAX_REQ)
		return palloc0(fc_size);

	/* 保持所有东西是最大对齐 */
	fc_size = MAXALIGN(fc_size);

	/* 需要更多空间？ */
	if (fc_size > fc_Conf->avail)
	{
		fc_Conf->firstfree = palloc0(COMPACT_ALLOC_CHUNK);
		fc_Conf->avail = COMPACT_ALLOC_CHUNK;
	}

	fc_result = (void *) fc_Conf->firstfree;
	fc_Conf->firstfree += fc_size;
	fc_Conf->avail -= fc_size;

	return fc_result;
}

#define cpalloc(size) fc_compact_palloc0(fc_Conf, size)
#define cpalloc0(size) fc_compact_palloc0(fc_Conf, size)

static char * fc_cpstrdup(IspellDict *fc_Conf, const char *fc_str)
{
	char	   *fc_res = cpalloc(strlen(fc_str) + 1);

	strcpy(fc_res, fc_str);
	return fc_res;
}


/*
 * 应用 lowerstr()，生成一个临时结果（在 buildCxt 中）。
 */
static char * fc_lowerstr_ctx(IspellDict *fc_Conf, const char *fc_src)
{
	MemoryContext fc_saveCtx;
	char	   *fc_dst;

	fc_saveCtx = MemoryContextSwitchTo(fc_Conf->buildCxt);
	fc_dst = lowerstr(fc_src);
	MemoryContextSwitchTo(fc_saveCtx);

	return fc_dst;
}

#define MAX_NORM 1024
#define MAXNORMLEN 256

#define STRNCMP(s,p)	strncmp( (s), (p), strlen(p) )
#define GETWCHAR(W,L,N,T) ( ((const uint8*)(W))[ ((T)==FF_PREFIX) ? (N) : ( (L) - 1 - (N) ) ] )
#define GETCHAR(A,N,T)	  GETWCHAR( (A)->repl, (A)->replen, N, T )

static char *VoidString = "";

static int fc_cmpspell(const void *fc_s1, const void *fc_s2)
{
	return strcmp((*(SPELL *const *) fc_s1)->word, (*(SPELL *const *) fc_s2)->word);
}

static int fc_cmpspellaffix(const void *fc_s1, const void *fc_s2)
{
	return strcmp((*(SPELL *const *) fc_s1)->p.flag,
				  (*(SPELL *const *) fc_s2)->p.flag);
}

static int fc_cmpcmdflag(const void *fc_f1, const void *fc_f2)
{
	CompoundAffixFlag *fc_fv1 = (CompoundAffixFlag *) fc_f1,
			   *fc_fv2 = (CompoundAffixFlag *) fc_f2;

	Assert(fc_fv1->flagMode == fc_fv2->flagMode);

	if (fc_fv1->flagMode == FM_NUM)
	{
		if (fc_fv1->flag.i == fc_fv2->flag.i)
			return 0;

		return (fc_fv1->flag.i > fc_fv2->flag.i) ? 1 : -1;
	}

	return strcmp(fc_fv1->flag.s, fc_fv2->flag.s);
}

static char * fc_findchar(char *fc_str, int fc_c)
{
	while (*fc_str)
	{
		if (t_iseq(fc_str, fc_c))
			return fc_str;
		fc_str += pg_mblen(fc_str);
	}

	return NULL;
}

static char * fc_findchar2(char *fc_str, int fc_c1, int fc_c2)
{
	while (*fc_str)
	{
		if (t_iseq(fc_str, fc_c1) || t_iseq(fc_str, fc_c2))
			return fc_str;
		fc_str += pg_mblen(fc_str);
	}

	return NULL;
}


/* 后缀树操作的向后字符串比较 */
static int fc_strbcmp(const unsigned char *fc_s1, const unsigned char *fc_s2)
{
	int			fc_l1 = strlen((const char *) fc_s1) - 1,
				fc_l2 = strlen((const char *) fc_s2) - 1;

	while (fc_l1 >= 0 && fc_l2 >= 0)
	{
		if (fc_s1[fc_l1] < fc_s2[fc_l2])
			return -1;
		if (fc_s1[fc_l1] > fc_s2[fc_l2])
			return 1;
		fc_l1--;
		fc_l2--;
	}
	if (fc_l1 < fc_l2)
		return -1;
	if (fc_l1 > fc_l2)
		return 1;

	return 0;
}

static int fc_strbncmp(const unsigned char *fc_s1, const unsigned char *fc_s2, size_t fc_count)
{
	int			fc_l1 = strlen((const char *) fc_s1) - 1,
				fc_l2 = strlen((const char *) fc_s2) - 1,
				fc_l = fc_count;

	while (fc_l1 >= 0 && fc_l2 >= 0 && fc_l > 0)
	{
		if (fc_s1[fc_l1] < fc_s2[fc_l2])
			return -1;
		if (fc_s1[fc_l1] > fc_s2[fc_l2])
			return 1;
		fc_l1--;
		fc_l2--;
		fc_l--;
	}
	if (fc_l == 0)
		return 0;
	if (fc_l1 < fc_l2)
		return -1;
	if (fc_l1 > fc_l2)
		return 1;
	return 0;
}

/*
 * 比较词缀。
 * 首先比较词缀的类型。前缀应放在词缀之前。
 * 如果类型相同，则比较可替换字符串。
 */
static int fc_cmpaffix(const void *fc_s1, const void *fc_s2)
{
	const AFFIX *fc_a1 = (const AFFIX *) fc_s1;
	const AFFIX *fc_a2 = (const AFFIX *) fc_s2;

	if (fc_a1->type < fc_a2->type)
		return -1;
	if (fc_a1->type > fc_a2->type)
		return 1;
	if (fc_a1->type == FF_PREFIX)
		return strcmp(fc_a1->repl, fc_a2->repl);
	else
		return fc_strbcmp((const unsigned char *) fc_a1->repl,
					   (const unsigned char *) fc_a2->repl);
}

/*
 * 从词缀标志集合（sflagset）中获取词缀标志。
 *
 * 多个标志可以存储在一个字符串中。标志可以用以下方式表示：
 * - 1 个字符 (FM_CHAR)。一个字符可以是 Unicode。
 * - 2 个字符 (FM_LONG)。一个字符可以是 Unicode。
 * - 从 1 到 65000 的数字 (FM_NUM)。
 *
 * 根据 flagMode，词缀字符串可以具有以下格式：
 * - FM_CHAR: ABCD
 *	 这里我们有 4 个标志：A、B、C 和 D
 * - FM_LONG: ABCDE*
 *	 这里我们有 3 个标志：AB、CD 和 E*
 * - FM_NUM: 200,205,50
 *	 这里我们有 3 个标志：200、205 和 50
 *
 * Conf: 当前字典。
 * sflagset: 词缀标志集合。返回对下一个
 *			 词缀标志开始的引用。
 * sflag: 从 sflagset 返回一个词缀标志。
 */
static void fc_getNextFlagFromString(IspellDict *fc_Conf, char **fc_sflagset, char *fc_sflag)
{
	int32		fc_s;
	char	   *fc_next,
			   *fc_sbuf = *fc_sflagset;
	int			fc_maxstep;
	bool		fc_stop = false;
	bool		fc_met_comma = false;

	fc_maxstep = (fc_Conf->flagMode == FM_LONG) ? 2 : 1;

	while (**fc_sflagset)
	{
		switch (fc_Conf->flagMode)
		{
			case FM_LONG:
			case FM_CHAR:
				COPYCHAR(fc_sflag, *fc_sflagset);
				fc_sflag += pg_mblen(*fc_sflagset);

				/* 转到下一个标志的开始 */
				*fc_sflagset += pg_mblen(*fc_sflagset);

				/* 检查我们是否获取了标志的所有字符 */
				fc_maxstep--;
				fc_stop = (fc_maxstep == 0);
				break;
			case FM_NUM:
				fc_s = strtol(*fc_sflagset, &fc_next, 10);
				if (*fc_sflagset == fc_next || errno == ERANGE)
					ereport(ERROR,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid affix flag \"%s\"", *fc_sflagset)));
				if (fc_s < 0 || fc_s > FLAGNUM_MAXSIZE)
					ereport(ERROR,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("affix flag \"%s\" is out of range",
									*fc_sflagset)));
				fc_sflag += sprintf(fc_sflag, "%0d", fc_s);

				/* 转到下一个标志的开始 */
				*fc_sflagset = fc_next;
				while (**fc_sflagset)
				{
					if (t_isdigit(*fc_sflagset))
					{
						if (!fc_met_comma)
							ereport(ERROR,
									(errcode(ERRCODE_CONFIG_FILE_ERROR),
									 errmsg("invalid affix flag \"%s\"",
											*fc_sflagset)));
						break;
					}
					else if (t_iseq(*fc_sflagset, ','))
					{
						if (fc_met_comma)
							ereport(ERROR,
									(errcode(ERRCODE_CONFIG_FILE_ERROR),
									 errmsg("invalid affix flag \"%s\"",
											*fc_sflagset)));
						fc_met_comma = true;
					}
					else if (!t_isspace(*fc_sflagset))
					{
						ereport(ERROR,
								(errcode(ERRCODE_CONFIG_FILE_ERROR),
								 errmsg("invalid character in affix flag \"%s\"",
										*fc_sflagset)));
					}

					*fc_sflagset += pg_mblen(*fc_sflagset);
				}
				fc_stop = true;
				break;
			default:
				elog(ERROR, "unrecognized type of Conf->flagMode: %d",
					 fc_Conf->flagMode);
		}

		if (fc_stop)
			break;
	}

	if (fc_Conf->flagMode == FM_LONG && fc_maxstep > 0)
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("invalid affix flag \"%s\" with \"long\" flag value",
						fc_sbuf)));

	*fc_sflag = '\0';
}

/*
 * 检查附加数据 Conf->AffixData[affix] 是否包含 affixflag。
 * 如果 .dict 文件没有实际使用该标志，则 Conf->AffixData[affix] 不包含 affixflag。
 *
 * Conf: 当前字典。
 * affix: Conf->AffixData 数组的索引。
 * affixflag: 附加标志。
 *
 * 如果字符串 Conf->AffixData[affix] 包含 affixflag，则返回 true，
 * 否则返回 false。
 */
static bool fc_IsAffixFlagInUse(IspellDict *fc_Conf, int fc_affix, const char *fc_affixflag)
{
	char	   *fc_flagcur;
	char		fc_flag[BUFSIZ];

	if (*fc_affixflag == 0)
		return true;

	Assert(fc_affix < fc_Conf->nAffixData);

	fc_flagcur = fc_Conf->AffixData[fc_affix];

	while (*fc_flagcur)
	{
		fc_getNextFlagFromString(fc_Conf, &fc_flagcur, fc_flag);
		/* 将 flagcur 中的第一个附加标志与 affixflag 进行比较 */
		if (strcmp(fc_flag, fc_affixflag) == 0)
			return true;
	}

	/* 找不到 affixflag */
	return false;
}

/*
 * 将新单词添加到临时数组 Spell 中。
 *
 * Conf: 当前字典。
 * word: 新单词。
 * flag: 附加标志集。可以通过 getNextFlagFromString() 获取单个标志。
 */
static void fc_NIAddSpell(IspellDict *fc_Conf, const char *fc_word, const char *fc_flag)
{
	if (fc_Conf->nspell >= fc_Conf->mspell)
	{
		if (fc_Conf->mspell)
		{
			fc_Conf->mspell *= 2;
			fc_Conf->Spell = (SPELL **) repalloc(fc_Conf->Spell, fc_Conf->mspell * sizeof(SPELL *));
		}
		else
		{
			fc_Conf->mspell = 1024 * 20;
			fc_Conf->Spell = (SPELL **) tmpalloc(fc_Conf->mspell * sizeof(SPELL *));
		}
	}
	fc_Conf->Spell[fc_Conf->nspell] = (SPELL *) tmpalloc(SPELLHDRSZ + strlen(fc_word) + 1);
	strcpy(fc_Conf->Spell[fc_Conf->nspell]->word, fc_word);
	fc_Conf->Spell[fc_Conf->nspell]->p.flag = (*fc_flag != '\0')
		? fc_cpstrdup(fc_Conf, fc_flag) : VoidString;
	fc_Conf->nspell++;
}

/*
 * 将字典导入临时数组 Spell 中。
 *
 * 注意调用者必须已经应用 get_tsearch_config_filename。
 *
 * Conf: 当前字典。
 * filename: .dict 文件的路径。
 */
void NIImportDictionary(IspellDict *fc_Conf, const char *fc_filename)
{
	tsearch_readline_state fc_trst;
	char	   *fc_line;

	if (!tsearch_readline_begin(&fc_trst, fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not open dictionary file \"%s\": %m",
						fc_filename)));

	while ((fc_line = tsearch_readline(&fc_trst)) != NULL)
	{
		char	   *fc_s,
				   *fc_pstr;

		/* 附加标志集 */
		const char *fc_flag;

		/* 从行中提取标志 */
		fc_flag = NULL;
		if ((fc_s = fc_findchar(fc_line, '/')))
		{
			*fc_s++ = '\0';
			fc_flag = fc_s;
			while (*fc_s)
			{
				/* 我们只允许单个编码标志以加快工作速度 */
				if (pg_mblen(fc_s) == 1 && t_isprint(fc_s) && !t_isspace(fc_s))
					fc_s++;
				else
				{
					*fc_s = '\0';
					break;
				}
			}
		}
		else
			fc_flag = "";

		/* 移除尾随空格 */
		fc_s = fc_line;
		while (*fc_s)
		{
			if (t_isspace(fc_s))
			{
				*fc_s = '\0';
				break;
			}
			fc_s += pg_mblen(fc_s);
		}
		fc_pstr = fc_lowerstr_ctx(fc_Conf, fc_line);

		fc_NIAddSpell(fc_Conf, fc_pstr, fc_flag);
		pfree(fc_pstr);

		pfree(fc_line);
	}
	tsearch_readline_end(&fc_trst);
}

/*
 * 在前缀树中搜索单词的基本形式。该单词是使用附加规则生成的。
 * 该规则可能未出现在一个基本形式的单词的附加集中。
 *
 * 例如，我们在 .dict 文件中有条目：
 * meter/GMD
 *
 * 带有标志 S 的附加规则：
 * SFX S   y     ies     [^aeiou]y
 * 在这里未出现。
 *
 * 带有标志 M 的附加规则：
 * SFX M   0     's         .
 * 在这里出现。
 *
 * Conf: 当前字典。
 * word: 单词的基本形式。
 * affixflag: 生成基本形式单词的附加标志。
 * flag: 用于与 StopMiddle->compoundflag 进行比较的复合标志。
 *
 * 如果在前缀树中找到该单词，则返回 1，否则返回 0。
 */
static int fc_FindWord(IspellDict *fc_Conf, const char *fc_word, const char *fc_affixflag, int fc_flag)
{
	SPNode	   *fc_node = fc_Conf->Dictionary;
	SPNodeData *fc_StopLow,
			   *fc_StopHigh,
			   *fc_StopMiddle;
	const uint8 *fc_ptr = (const uint8 *) fc_word;

	fc_flag &= FF_COMPOUNDFLAGMASK;

	while (fc_node && *fc_ptr)
	{
		fc_StopLow = fc_node->data;
		fc_StopHigh = fc_node->data + fc_node->length;
		while (fc_StopLow < fc_StopHigh)
		{
			fc_StopMiddle = fc_StopLow + ((fc_StopHigh - fc_StopLow) >> 1);
			if (fc_StopMiddle->val == *fc_ptr)
			{
				if (*(fc_ptr + 1) == '\0' && fc_StopMiddle->isword)
				{
					if (fc_flag == 0)
					{
						/*
 * 该单词只能与另一个单词组合而成。并且在标志参数中没有标志指示
 * 我们搜索复合词。
 */
						if (fc_StopMiddle->compoundflag & FF_COMPOUNDONLY)
							return 0;
					}
					else if ((fc_flag & fc_StopMiddle->compoundflag) == 0)
						return 0;

					/*
 * 检查该附加规则在索引 StopMiddle->affix 的附加集中是否存在。
 */
					if (fc_IsAffixFlagInUse(fc_Conf, fc_StopMiddle->affix, fc_affixflag))
						return 1;
				}
				fc_node = fc_StopMiddle->node;
				fc_ptr++;
				break;
			}
			else if (fc_StopMiddle->val < *fc_ptr)
				fc_StopLow = fc_StopMiddle + 1;
			else
				fc_StopHigh = fc_StopMiddle;
		}
		if (fc_StopLow >= fc_StopHigh)
			break;
	}
	return 0;
}

/*
 * 用于附加中的正则表达式的上下文重置/删除回调
 */
static void fc_regex_affix_deletion_callback(void *fc_arg)
{
	aff_regex_struct *fc_pregex = (aff_regex_struct *) fc_arg;

	pg_regfree(&(fc_pregex->regex));
}

/*
 * 将新的附加规则添加到附加字段中。
 *
 * Conf: 当前字典。
 * flag: 附加标志（在下面的示例中为 '\'）。
 * flagflags: 该附加规则的 flagval 字段中的标志集。该集合
 *            在添加字符串（repl）中的 '/' 字符后列出。
 *
 *            例如在 hunspell_sample.affix 中的 L 标志：
 *            SFX \   0 Y/L [^Y]
 *
 * mask: 搜索条件（在上面的示例中为 '[^Y]'）。
 * find: 从单词的开头（前缀）或结尾（后缀）去除字符
 *       （在上面的示例中为 '0'，0 表示没有去除字符）。
 * repl: 去除后添加的字符串（在上面的示例中为 'Y'）。
 * type: FF_SUFFIX 或 FF_PREFIX。
 */
static void fc_NIAddAffix(IspellDict *fc_Conf, const char *fc_flag, char fc_flagflags, const char *fc_mask,
		   const char *fc_find, const char *fc_repl, int fc_type)
{
	AFFIX	   *fc_Affix;

	if (fc_Conf->naffixes >= fc_Conf->maffixes)
	{
		if (fc_Conf->maffixes)
		{
			fc_Conf->maffixes *= 2;
			fc_Conf->Affix = (AFFIX *) repalloc((void *) fc_Conf->Affix, fc_Conf->maffixes * sizeof(AFFIX));
		}
		else
		{
			fc_Conf->maffixes = 16;
			fc_Conf->Affix = (AFFIX *) palloc(fc_Conf->maffixes * sizeof(AFFIX));
		}
	}

	fc_Affix = fc_Conf->Affix + fc_Conf->naffixes;

	/* 此附加规则可以应用于任何结尾的单词 */
	if (strcmp(fc_mask, ".") == 0 || *fc_mask == '\0')
	{
		fc_Affix->issimple = 1;
		fc_Affix->isregis = 0;
	}
	/* 此附加规则将使用 regis 搜索单词结尾 */
	else if (RS_isRegis(fc_mask))
	{
		fc_Affix->issimple = 0;
		fc_Affix->isregis = 1;
		RS_compile(&(fc_Affix->reg.regis), (fc_type == FF_SUFFIX),
				   *fc_mask ? fc_mask : VoidString);
	}
	/* 这个附加规则将使用 regex_t 搜索单词结尾 */
	else
	{
		int			fc_masklen;
		int			fc_wmasklen;
		int			fc_err;
		pg_wchar   *fc_wmask;
		char	   *fc_tmask;
		aff_regex_struct *fc_pregex;

		fc_Affix->issimple = 0;
		fc_Affix->isregis = 0;
		fc_tmask = (char *) tmpalloc(strlen(fc_mask) + 3);
		if (fc_type == FF_SUFFIX)
			sprintf(fc_tmask, "%s$", fc_mask);
		else
			sprintf(fc_tmask, "^%s", fc_mask);

		fc_masklen = strlen(fc_tmask);
		fc_wmask = (pg_wchar *) tmpalloc((fc_masklen + 1) * sizeof(pg_wchar));
		fc_wmasklen = pg_mb2wchar_with_len(fc_tmask, fc_wmask, fc_masklen);

		/*
		 * 正则引擎使用 malloc 而不是 palloc 来存储自己的内容，因此我们
		 * 必须安排在字典上下文清除时显式清理正则表达式。这意味着 regex_t 必须
		 * 保持在上下文中的固定位置；我们不能直接将其保存在 AFFIX 结构中，因为
		 * 我们可能会对 AFFIX 数组进行排序和调整大小。
		 */
		fc_Affix->reg.pregex = fc_pregex = palloc(sizeof(aff_regex_struct));

		fc_err = pg_regcomp(&(fc_pregex->regex), fc_wmask, fc_wmasklen,
						 REG_ADVANCED | REG_NOSUB,
						 DEFAULT_COLLATION_OID);
		if (fc_err)
		{
			char		fc_errstr[100];

			pg_regerror(fc_err, &(fc_pregex->regex), fc_errstr, sizeof(fc_errstr));
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_REGULAR_EXPRESSION),
					 errmsg("invalid regular expression: %s", fc_errstr)));
		}

		fc_pregex->mcallback.func = fc_regex_affix_deletion_callback;
		fc_pregex->mcallback.arg = (void *) fc_pregex;
		MemoryContextRegisterResetCallback(CurrentMemoryContext,
										   &fc_pregex->mcallback);
	}

	fc_Affix->flagflags = fc_flagflags;
	if ((fc_Affix->flagflags & FF_COMPOUNDONLY) || (fc_Affix->flagflags & FF_COMPOUNDPERMITFLAG))
	{
		if ((fc_Affix->flagflags & FF_COMPOUNDFLAG) == 0)
			fc_Affix->flagflags |= FF_COMPOUNDFLAG;
	}
	fc_Affix->flag = fc_cpstrdup(fc_Conf, fc_flag);
	fc_Affix->type = fc_type;

	fc_Affix->find = (fc_find && *fc_find) ? fc_cpstrdup(fc_Conf, fc_find) : VoidString;
	if ((fc_Affix->replen = strlen(fc_repl)) > 0)
		fc_Affix->repl = fc_cpstrdup(fc_Conf, fc_repl);
	else
		fc_Affix->repl = VoidString;
	fc_Conf->naffixes++;
}

/* parse_affentry() 和相关函数的解析状态 */
#define PAE_WAIT_MASK	0
#define PAE_INMASK		1
#define PAE_WAIT_FIND	2
#define PAE_INFIND		3
#define PAE_WAIT_REPL	4
#define PAE_INREPL		5
#define PAE_WAIT_TYPE	6
#define PAE_WAIT_FLAG	7

/*
 * 解析 .affix 文件行的下一个以空格分隔的字段。
 *
 * *str 是输入指针（将指向字段之后）
 * next 是复制字段值的地方，以 null 结束
 *
 * "next" 的缓冲区必须为 BUFSIZ 大小；我们截断输入以适应。
 *
 * 如果找到字段返回 true，未找到则返回 false。
 */
static bool fc_get_nextfield(char **fc_str, char *fc_next)
{
	int			fc_state = PAE_WAIT_MASK;
	int			fc_avail = BUFSIZ;

	while (**fc_str)
	{
		if (fc_state == PAE_WAIT_MASK)
		{
			if (t_iseq(*fc_str, '#'))
				return false;
			else if (!t_isspace(*fc_str))
			{
				int			fc_clen = pg_mblen(*fc_str);

				if (fc_clen < fc_avail)
				{
					COPYCHAR(fc_next, *fc_str);
					fc_next += fc_clen;
					fc_avail -= fc_clen;
				}
				fc_state = PAE_INMASK;
			}
		}
		else					/* state == PAE_INMASK */
		{
			if (t_isspace(*fc_str))
			{
				*fc_next = '\0';
				return true;
			}
			else
			{
				int			fc_clen = pg_mblen(*fc_str);

				if (fc_clen < fc_avail)
				{
					COPYCHAR(fc_next, *fc_str);
					fc_next += fc_clen;
					fc_avail -= fc_clen;
				}
			}
		}
		*fc_str += pg_mblen(*fc_str);
	}

	*fc_next = '\0';

	return (fc_state == PAE_INMASK);	/* 如果我们得到了非空字段则没问题 */
}

/*
 * 解析 MySpell 或 Hunspell 格式的 .affix 文件条目。
 *
 * .affix 文件条目的格式如下：
 * - 头部
 *	 <type>  <flag>  <cross_flag>  <flag_count>
 * - 头部后的字段：
 *	 <type>  <flag>  <find>  <replace>	<mask>
 *
 * str 是输入行
 * 字段值返回到 type 等，必须是 BUFSIZ 大小的缓冲区。
 *
 * 返回找到的字段数量；任何省略的字段将设置为空字符串。
 */
static int fc_parse_ooaffentry(char *fc_str, char *fc_type, char *fc_flag, char *fc_find,
				 char *fc_repl, char *fc_mask)
{
	int			fc_state = PAE_WAIT_TYPE;
	int			fc_fields_read = 0;
	bool		fc_valid = false;

	*fc_type = *fc_flag = *fc_find = *fc_repl = *fc_mask = '\0';

	while (*fc_str)
	{
		switch (fc_state)
		{
			case PAE_WAIT_TYPE:
				fc_valid = fc_get_nextfield(&fc_str, fc_type);
				fc_state = PAE_WAIT_FLAG;
				break;
			case PAE_WAIT_FLAG:
				fc_valid = fc_get_nextfield(&fc_str, fc_flag);
				fc_state = PAE_WAIT_FIND;
				break;
			case PAE_WAIT_FIND:
				fc_valid = fc_get_nextfield(&fc_str, fc_find);
				fc_state = PAE_WAIT_REPL;
				break;
			case PAE_WAIT_REPL:
				fc_valid = fc_get_nextfield(&fc_str, fc_repl);
				fc_state = PAE_WAIT_MASK;
				break;
			case PAE_WAIT_MASK:
				fc_valid = fc_get_nextfield(&fc_str, fc_mask);
				fc_state = -1;		/* 强制循环退出 */
				break;
			default:
				elog(ERROR, "unrecognized state in parse_ooaffentry: %d",
					 fc_state);
				break;
		}
		if (fc_valid)
			fc_fields_read++;
		else
			break;				/* 早期行结束 */
		if (fc_state < 0)
			break;				/* 得到了所有字段 */
	}

	return fc_fields_read;
}

/*
 * 解析 Ispell 格式的 .affix 文件条目
 *
 * .affix 文件条目的格式如下：
 * <mask>  >  [-<find>,]<replace>
 */
static bool fc_parse_affentry(char *fc_str, char *fc_mask, char *fc_find, char *fc_repl)
{
	int			fc_state = PAE_WAIT_MASK;
	char	   *fc_pmask = fc_mask,
			   *fc_pfind = fc_find,
			   *fc_prepl = fc_repl;

	*fc_mask = *fc_find = *fc_repl = '\0';

	while (*fc_str)
	{
		if (fc_state == PAE_WAIT_MASK)
		{
			if (t_iseq(fc_str, '#'))
				return false;
			else if (!t_isspace(fc_str))
			{
				COPYCHAR(fc_pmask, fc_str);
				fc_pmask += pg_mblen(fc_str);
				fc_state = PAE_INMASK;
			}
		}
		else if (fc_state == PAE_INMASK)
		{
			if (t_iseq(fc_str, '>'))
			{
				*fc_pmask = '\0';
				fc_state = PAE_WAIT_FIND;
			}
			else if (!t_isspace(fc_str))
			{
				COPYCHAR(fc_pmask, fc_str);
				fc_pmask += pg_mblen(fc_str);
			}
		}
		else if (fc_state == PAE_WAIT_FIND)
		{
			if (t_iseq(fc_str, '-'))
			{
				fc_state = PAE_INFIND;
			}
			else if (t_isalpha(fc_str) || t_iseq(fc_str, '\'') /* 英文的 's */ )
			{
				COPYCHAR(fc_prepl, fc_str);
				fc_prepl += pg_mblen(fc_str);
				fc_state = PAE_INREPL;
			}
			else if (!t_isspace(fc_str))
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("syntax error")));
		}
		else if (fc_state == PAE_INFIND)
		{
			if (t_iseq(fc_str, ','))
			{
				*fc_pfind = '\0';
				fc_state = PAE_WAIT_REPL;
			}
			else if (t_isalpha(fc_str))
			{
				COPYCHAR(fc_pfind, fc_str);
				fc_pfind += pg_mblen(fc_str);
			}
			else if (!t_isspace(fc_str))
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("syntax error")));
		}
		else if (fc_state == PAE_WAIT_REPL)
		{
			if (t_iseq(fc_str, '-'))
			{
				break;			/* void repl */
			}
			else if (t_isalpha(fc_str))
			{
				COPYCHAR(fc_prepl, fc_str);
				fc_prepl += pg_mblen(fc_str);
				fc_state = PAE_INREPL;
			}
			else if (!t_isspace(fc_str))
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("syntax error")));
		}
		else if (fc_state == PAE_INREPL)
		{
			if (t_iseq(fc_str, '#'))
			{
				*fc_prepl = '\0';
				break;
			}
			else if (t_isalpha(fc_str))
			{
				COPYCHAR(fc_prepl, fc_str);
				fc_prepl += pg_mblen(fc_str);
			}
			else if (!t_isspace(fc_str))
				ereport(ERROR,
						(errcode(ERRCODE_CONFIG_FILE_ERROR),
						 errmsg("syntax error")));
		}
		else
			elog(ERROR, "unrecognized state in parse_affentry: %d", fc_state);

		fc_str += pg_mblen(fc_str);
	}

	*fc_pmask = *fc_pfind = *fc_prepl = '\0';

	return (*fc_mask && (*fc_find || *fc_repl));
}

/*
 * 根据标志类型设置 Hunspell 选项。
 */
static void fc_setCompoundAffixFlagValue(IspellDict *fc_Conf, CompoundAffixFlag *fc_entry,
						  char *fc_s, uint32 fc_val)
{
	if (fc_Conf->flagMode == FM_NUM)
	{
		char	   *fc_next;
		int			fc_i;

		fc_i = strtol(fc_s, &fc_next, 10);
		if (fc_s == fc_next || errno == ERANGE)
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("invalid affix flag \"%s\"", fc_s)));
		if (fc_i < 0 || fc_i > FLAGNUM_MAXSIZE)
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("affix flag \"%s\" is out of range", fc_s)));

		fc_entry->flag.i = fc_i;
	}
	else
		fc_entry->flag.s = fc_cpstrdup(fc_Conf, fc_s);

	fc_entry->flagMode = fc_Conf->flagMode;
	fc_entry->value = fc_val;
}

/*
 * 为附加参数与附加标志设置对应关系。
 *
 * Conf: 当前字典。
 * s: 字符串中的附加标志。
 * val: 附加参数。
 */
static void fc_addCompoundAffixFlagValue(IspellDict *fc_Conf, char *fc_s, uint32 fc_val)
{
	CompoundAffixFlag *fc_newValue;
	char		fc_sbuf[BUFSIZ];
	char	   *fc_sflag;
	int			fc_clen;

	while (*fc_s && t_isspace(fc_s))
		fc_s += pg_mblen(fc_s);

	if (!*fc_s)
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("syntax error")));

	/* 获取不带 \n 的标志 */
	fc_sflag = fc_sbuf;
	while (*fc_s && !t_isspace(fc_s) && *fc_s != '\n')
	{
		fc_clen = pg_mblen(fc_s);
		COPYCHAR(fc_sflag, fc_s);
		fc_sflag += fc_clen;
		fc_s += fc_clen;
	}
	*fc_sflag = '\0';

	/* 调整数组大小或为数组 CompoundAffixFlag 分配内存 */
	if (fc_Conf->nCompoundAffixFlag >= fc_Conf->mCompoundAffixFlag)
	{
		if (fc_Conf->mCompoundAffixFlag)
		{
			fc_Conf->mCompoundAffixFlag *= 2;
			fc_Conf->CompoundAffixFlags = (CompoundAffixFlag *)
				repalloc((void *) fc_Conf->CompoundAffixFlags,
						 fc_Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
		}
		else
		{
			fc_Conf->mCompoundAffixFlag = 10;
			fc_Conf->CompoundAffixFlags = (CompoundAffixFlag *)
				tmpalloc(fc_Conf->mCompoundAffixFlag * sizeof(CompoundAffixFlag));
		}
	}

	fc_newValue = fc_Conf->CompoundAffixFlags + fc_Conf->nCompoundAffixFlag;

	fc_setCompoundAffixFlagValue(fc_Conf, fc_newValue, fc_sbuf, fc_val);

	fc_Conf->usecompound = true;
	fc_Conf->nCompoundAffixFlag++;
}

/*
 * 返回一组对应于附加标志 s 的附加参数。
 */
static int fc_getCompoundAffixFlagValue(IspellDict *fc_Conf, char *fc_s)
{
	uint32		fc_flag = 0;
	CompoundAffixFlag *fc_found,
				fc_key;
	char		fc_sflag[BUFSIZ];
	char	   *fc_flagcur;

	if (fc_Conf->nCompoundAffixFlag == 0)
		return 0;

	fc_flagcur = fc_s;
	while (*fc_flagcur)
	{
		fc_getNextFlagFromString(fc_Conf, &fc_flagcur, fc_sflag);
		fc_setCompoundAffixFlagValue(fc_Conf, &fc_key, fc_sflag, 0);

		fc_found = (CompoundAffixFlag *)
			bsearch(&fc_key, (void *) fc_Conf->CompoundAffixFlags,
					fc_Conf->nCompoundAffixFlag, sizeof(CompoundAffixFlag),
					fc_cmpcmdflag);
		if (fc_found != NULL)
			fc_flag |= fc_found->value;
	}

	return fc_flag;
}

/*
 * 使用 s 参数返回一个标志集。
 *
 * 如果 Conf->useFlagAliases 为 true，则 s 参数为
 * Conf->AffixData 数组的索引，函数返回其条目。
 * 否则，函数返回 s 参数。
 */
static char * fc_getAffixFlagSet(IspellDict *fc_Conf, char *fc_s)
{
	if (fc_Conf->useFlagAliases && *fc_s != '\0')
	{
		int			fc_curaffix;
		char	   *fc_end;

		fc_curaffix = strtol(fc_s, &fc_end, 10);
		if (fc_s == fc_end || errno == ERANGE)
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("invalid affix alias \"%s\"", fc_s)));

		if (fc_curaffix > 0 && fc_curaffix < fc_Conf->nAffixData)

			/*
			 * 不要从 curaffix 中减去 1，因为在 NIImportOOAffixes 中添加了空字符串
			 */
			return fc_Conf->AffixData[fc_curaffix];
		else if (fc_curaffix > fc_Conf->nAffixData)
			ereport(ERROR,
					(errcode(ERRCODE_CONFIG_FILE_ERROR),
					 errmsg("invalid affix alias \"%s\"", fc_s)));
		return VoidString;
	}
	else
		return fc_s;
}

/*
 * 导入遵循 MySpell 或 Hunspell 格式的附加文件。
 *
 * Conf: 当前字典。
 * filename: .affix 文件的路径。
 */
static void fc_NIImportOOAffixes(IspellDict *fc_Conf, const char *fc_filename)
{
	char		fc_type[BUFSIZ],
			   *fc_ptype = NULL;
	char		fc_sflag[BUFSIZ];
	char		fc_mask[BUFSIZ],
			   *fc_pmask;
	char		fc_find[BUFSIZ],
			   *fc_pfind;
	char		fc_repl[BUFSIZ],
			   *fc_prepl;
	bool		fc_isSuffix = false;
	int			fc_naffix = 0,
				fc_curaffix = 0;
	int			fc_sflaglen = 0;
	char		fc_flagflags = 0;
	tsearch_readline_state fc_trst;
	char	   *fc_recoded;

	/* 读取文件以查找任何标志 */
	fc_Conf->usecompound = false;
	fc_Conf->useFlagAliases = false;
	fc_Conf->flagMode = FM_CHAR;

	if (!tsearch_readline_begin(&fc_trst, fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not open affix file \"%s\": %m",
						fc_filename)));

	while ((fc_recoded = tsearch_readline(&fc_trst)) != NULL)
	{
		if (*fc_recoded == '\0' || t_isspace(fc_recoded) || t_iseq(fc_recoded, '#'))
		{
			pfree(fc_recoded);
			continue;
		}

		if (STRNCMP(fc_recoded, "COMPOUNDFLAG") == 0)
			fc_addCompoundAffixFlagValue(fc_Conf, fc_recoded + strlen("COMPOUNDFLAG"),
									  FF_COMPOUNDFLAG);
		else if (STRNCMP(fc_recoded, "COMPOUNDBEGIN") == 0)
			fc_addCompoundAffixFlagValue(fc_Conf, fc_recoded + strlen("COMPOUNDBEGIN"),
									  FF_COMPOUNDBEGIN);
		else if (STRNCMP(fc_recoded, "COMPOUNDLAST") == 0)
			fc_addCompoundAffixFlagValue(fc_Conf, fc_recoded + strlen("COMPOUNDLAST"),
									  FF_COMPOUNDLAST);
		/* COMPOUNDLAST 和 COMPOUNDEND 是同义词 */
		else if (STRNCMP(fc_recoded, "COMPOUNDEND") == 0)
			fc_addCompoundAffixFlagValue(fc_Conf, fc_recoded + strlen("COMPOUNDEND"),
									  FF_COMPOUNDLAST);
		else if (STRNCMP(fc_recoded, "COMPOUNDMIDDLE") == 0)
			fc_addCompoundAffixFlagValue(fc_Conf, fc_recoded + strlen("COMPOUNDMIDDLE"),
									  FF_COMPOUNDMIDDLE);
		else if (STRNCMP(fc_recoded, "ONLYINCOMPOUND") == 0)
			fc_addCompoundAffixFlagValue(fc_Conf, fc_recoded + strlen("ONLYINCOMPOUND"),
									  FF_COMPOUNDONLY);
		else if (STRNCMP(fc_recoded, "COMPOUNDPERMITFLAG") == 0)
			fc_addCompoundAffixFlagValue(fc_Conf,
									  fc_recoded + strlen("COMPOUNDPERMITFLAG"),
									  FF_COMPOUNDPERMITFLAG);
		else if (STRNCMP(fc_recoded, "COMPOUNDFORBIDFLAG") == 0)
			fc_addCompoundAffixFlagValue(fc_Conf,
									  fc_recoded + strlen("COMPOUNDFORBIDFLAG"),
									  FF_COMPOUNDFORBIDFLAG);
		else if (STRNCMP(fc_recoded, "FLAG") == 0)
		{
			char	   *fc_s = fc_recoded + strlen("FLAG");

			while (*fc_s && t_isspace(fc_s))
				fc_s += pg_mblen(fc_s);

			if (*fc_s)
			{
				if (STRNCMP(fc_s, "long") == 0)
					fc_Conf->flagMode = FM_LONG;
				else if (STRNCMP(fc_s, "num") == 0)
					fc_Conf->flagMode = FM_NUM;
				else if (STRNCMP(fc_s, "default") != 0)
					ereport(ERROR,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("Ispell dictionary supports only "
									"\"default\", \"long\", "
									"and \"num\" flag values")));
			}
		}

		pfree(fc_recoded);
	}
	tsearch_readline_end(&fc_trst);

	if (fc_Conf->nCompoundAffixFlag > 1)
		qsort((void *) fc_Conf->CompoundAffixFlags, fc_Conf->nCompoundAffixFlag,
			  sizeof(CompoundAffixFlag), fc_cmpcmdflag);

	if (!tsearch_readline_begin(&fc_trst, fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not open affix file \"%s\": %m",
						fc_filename)));

	while ((fc_recoded = tsearch_readline(&fc_trst)) != NULL)
	{
		int			fc_fields_read;

		if (*fc_recoded == '\0' || t_isspace(fc_recoded) || t_iseq(fc_recoded, '#'))
			goto nextline;

		fc_fields_read = fc_parse_ooaffentry(fc_recoded, fc_type, fc_sflag, fc_find, fc_repl, fc_mask);

		if (fc_ptype)
			pfree(fc_ptype);
		fc_ptype = fc_lowerstr_ctx(fc_Conf, fc_type);

		/* 首先尝试解析 AF 参数（别名压缩） */
		if (STRNCMP(fc_ptype, "af") == 0)
		{
			/* 第一行是别名的数量 */
			if (!fc_Conf->useFlagAliases)
			{
				fc_Conf->useFlagAliases = true;
				fc_naffix = atoi(fc_sflag);
				if (fc_naffix <= 0)
					ereport(ERROR,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid number of flag vector aliases")));

				/* 也为空标志集保留位置 */
				fc_naffix++;

				fc_Conf->AffixData = (char **) palloc0(fc_naffix * sizeof(char *));
				fc_Conf->lenAffixData = fc_Conf->nAffixData = fc_naffix;

				/* 将空标志集添加到 AffixData */
				fc_Conf->AffixData[fc_curaffix] = VoidString;
				fc_curaffix++;
			}
			/* 其他行是别名 */
			else
			{
				if (fc_curaffix < fc_naffix)
				{
					fc_Conf->AffixData[fc_curaffix] = fc_cpstrdup(fc_Conf, fc_sflag);
					fc_curaffix++;
				}
				else
					ereport(ERROR,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("number of aliases exceeds specified number %d",
									fc_naffix - 1)));
			}
			goto nextline;
		}
		/* 否则尝试解析前缀和后缀 */
		if (fc_fields_read < 4 ||
			(STRNCMP(fc_ptype, "sfx") != 0 && STRNCMP(fc_ptype, "pfx") != 0))
			goto nextline;

		fc_sflaglen = strlen(fc_sflag);
		if (fc_sflaglen == 0
			|| (fc_sflaglen > 1 && fc_Conf->flagMode == FM_CHAR)
			|| (fc_sflaglen > 2 && fc_Conf->flagMode == FM_LONG))
			goto nextline;

		/*--------
		 * 附加头部。例如：
		 * SFX \ N 1
		 *--------
		 */
		if (fc_fields_read == 4)
		{
			fc_isSuffix = (STRNCMP(fc_ptype, "sfx") == 0);
			if (t_iseq(fc_find, 'y') || t_iseq(fc_find, 'Y'))
				fc_flagflags = FF_CROSSPRODUCT;
			else
				fc_flagflags = 0;
		}
		/*--------
		 * 附加字段。例如：
		 * SFX \   0	Y/L [^Y]
		 *--------
		 */
		else
		{
			char	   *fc_ptr;
			int			fc_aflg = 0;

			/* 在 '/' 后获取标志（标志区分大小写） */
			if ((fc_ptr = strchr(fc_repl, '/')) != NULL)
				fc_aflg |= fc_getCompoundAffixFlagValue(fc_Conf,
												  fc_getAffixFlagSet(fc_Conf,
																  fc_ptr + 1));
			/* 获取 '/' 前字符串的小写版本 */
			fc_prepl = fc_lowerstr_ctx(fc_Conf, fc_repl);
			if ((fc_ptr = strchr(fc_prepl, '/')) != NULL)
				*fc_ptr = '\0';
			fc_pfind = fc_lowerstr_ctx(fc_Conf, fc_find);
			fc_pmask = fc_lowerstr_ctx(fc_Conf, fc_mask);
			if (t_iseq(fc_find, '0'))
				*fc_pfind = '\0';
			if (t_iseq(fc_repl, '0'))
				*fc_prepl = '\0';

			fc_NIAddAffix(fc_Conf, fc_sflag, fc_flagflags | fc_aflg, fc_pmask, fc_pfind, fc_prepl,
					   fc_isSuffix ? FF_SUFFIX : FF_PREFIX);
			pfree(fc_prepl);
			pfree(fc_pfind);
			pfree(fc_pmask);
		}

nextline:
		pfree(fc_recoded);
	}

	tsearch_readline_end(&fc_trst);
	if (fc_ptype)
		pfree(fc_ptype);
}

/*
 * 导入附加
 *
 * 注意调用者必须已经应用get_tsearch_config_filename
 *
 * 该函数负责解析ispell（“旧格式”）附加文件。
 * 如果我们发现文件包含新格式命令，我们将工作交给NIImportOOAffixes()，
 * 它将重新读取整个文件。
 */
void NIImportAffixes(IspellDict *fc_Conf, const char *fc_filename)
{
	char	   *fc_pstr = NULL;
	char		fc_flag[BUFSIZ];
	char		fc_mask[BUFSIZ];
	char		fc_find[BUFSIZ];
	char		fc_repl[BUFSIZ];
	char	   *fc_s;
	bool		fc_suffixes = false;
	bool		fc_prefixes = false;
	char		fc_flagflags = 0;
	tsearch_readline_state fc_trst;
	bool		fc_oldformat = false;
	char	   *fc_recoded = NULL;

	if (!tsearch_readline_begin(&fc_trst, fc_filename))
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("could not open affix file \"%s\": %m",
						fc_filename)));

	fc_Conf->usecompound = false;
	fc_Conf->useFlagAliases = false;
	fc_Conf->flagMode = FM_CHAR;

	while ((fc_recoded = tsearch_readline(&fc_trst)) != NULL)
	{
		fc_pstr = lowerstr(fc_recoded);

		/* 跳过注释和空行 */
		if (*fc_pstr == '#' || *fc_pstr == '\n')
			goto nextline;

		if (STRNCMP(fc_pstr, "compoundwords") == 0)
		{
			/* 在非小写字符串中查找不区分大小写的L标志 */
			fc_s = fc_findchar2(fc_recoded, 'l', 'L');
			if (fc_s)
			{
				while (*fc_s && !t_isspace(fc_s))
					fc_s += pg_mblen(fc_s);
				while (*fc_s && t_isspace(fc_s))
					fc_s += pg_mblen(fc_s);

				if (*fc_s && pg_mblen(fc_s) == 1)
				{
					fc_addCompoundAffixFlagValue(fc_Conf, fc_s, FF_COMPOUNDFLAG);
					fc_Conf->usecompound = true;
				}
				fc_oldformat = true;
				goto nextline;
			}
		}
		if (STRNCMP(fc_pstr, "suffixes") == 0)
		{
			fc_suffixes = true;
			fc_prefixes = false;
			fc_oldformat = true;
			goto nextline;
		}
		if (STRNCMP(fc_pstr, "prefixes") == 0)
		{
			fc_suffixes = false;
			fc_prefixes = true;
			fc_oldformat = true;
			goto nextline;
		}
		if (STRNCMP(fc_pstr, "flag") == 0)
		{
			fc_s = fc_recoded + 4;	/* 我们需要非小写字符串 */
			fc_flagflags = 0;

			while (*fc_s && t_isspace(fc_s))
				fc_s += pg_mblen(fc_s);

			if (*fc_s == '*')
			{
				fc_flagflags |= FF_CROSSPRODUCT;
				fc_s++;
			}
			else if (*fc_s == '~')
			{
				fc_flagflags |= FF_COMPOUNDONLY;
				fc_s++;
			}

			if (*fc_s == '\\')
				fc_s++;

			/*
			 * 旧格式标志是单个ASCII字符；我们期望它后面紧跟EOL，空格或':'。否则这是一个
			 * 新格式标志命令。
			 */
			if (*fc_s && pg_mblen(fc_s) == 1)
			{
				COPYCHAR(fc_flag, fc_s);
				fc_flag[1] = '\0';

				fc_s++;
				if (*fc_s == '\0' || *fc_s == '#' || *fc_s == '\n' || *fc_s == ':' ||
					t_isspace(fc_s))
				{
					fc_oldformat = true;
					goto nextline;
				}
			}
			goto isnewformat;
		}
		if (STRNCMP(fc_recoded, "COMPOUNDFLAG") == 0 ||
			STRNCMP(fc_recoded, "COMPOUNDMIN") == 0 ||
			STRNCMP(fc_recoded, "PFX") == 0 ||
			STRNCMP(fc_recoded, "SFX") == 0)
			goto isnewformat;

		if ((!fc_suffixes) && (!fc_prefixes))
			goto nextline;

		if (!fc_parse_affentry(fc_pstr, fc_mask, fc_find, fc_repl))
			goto nextline;

		fc_NIAddAffix(fc_Conf, fc_flag, fc_flagflags, fc_mask, fc_find, fc_repl, fc_suffixes ? FF_SUFFIX : FF_PREFIX);

nextline:
		pfree(fc_recoded);
		pfree(fc_pstr);
	}
	tsearch_readline_end(&fc_trst);
	return;

isnewformat:
	if (fc_oldformat)
		ereport(ERROR,
				(errcode(ERRCODE_CONFIG_FILE_ERROR),
				 errmsg("affix file contains both old-style and new-style commands")));
	tsearch_readline_end(&fc_trst);

	fc_NIImportOOAffixes(fc_Conf, fc_filename);
}

/*
 * 合并两个附加标志集并将新的附加标志集存储到
 * Conf->AffixData中。
 *
 * 返回新的附加标志集的索引。
 */
static int fc_MergeAffix(IspellDict *fc_Conf, int fc_a1, int fc_a2)
{
	char	  **fc_ptr;

	Assert(fc_a1 < fc_Conf->nAffixData && fc_a2 < fc_Conf->nAffixData);

	/* 如果一个附加标志为空，则不要合并附加标志 */
	if (*fc_Conf->AffixData[fc_a1] == '\0')
		return fc_a2;
	else if (*fc_Conf->AffixData[fc_a2] == '\0')
		return fc_a1;

	/* 如果AffixData的空间不足，双倍其大小 */
	if (fc_Conf->nAffixData + 1 >= fc_Conf->lenAffixData)
	{
		fc_Conf->lenAffixData *= 2;
		fc_Conf->AffixData = (char **) repalloc(fc_Conf->AffixData,
											 sizeof(char *) * fc_Conf->lenAffixData);
	}

	fc_ptr = fc_Conf->AffixData + fc_Conf->nAffixData;
	if (fc_Conf->flagMode == FM_NUM)
	{
		*fc_ptr = cpalloc(strlen(fc_Conf->AffixData[fc_a1]) +
					   strlen(fc_Conf->AffixData[fc_a2]) +
					   1 /* 逗号 */ + 1 /* \0 */ );
		sprintf(*fc_ptr, "%s,%s", fc_Conf->AffixData[fc_a1], fc_Conf->AffixData[fc_a2]);
	}
	else
	{
		*fc_ptr = cpalloc(strlen(fc_Conf->AffixData[fc_a1]) +
					   strlen(fc_Conf->AffixData[fc_a2]) +
					   1 /* \0 */ );
		sprintf(*fc_ptr, "%s%s", fc_Conf->AffixData[fc_a1], fc_Conf->AffixData[fc_a2]);
	}
	fc_ptr++;
	*fc_ptr = NULL;
	fc_Conf->nAffixData++;

	return fc_Conf->nAffixData - 1;
}

/*
 * 返回与给定索引的附加标志集对应的附加参数集。
 */
static uint32 fc_makeCompoundFlags(IspellDict *fc_Conf, int fc_affix)
{
	Assert(fc_affix < fc_Conf->nAffixData);

	return (fc_getCompoundAffixFlagValue(fc_Conf, fc_Conf->AffixData[fc_affix]) &
			FF_COMPOUNDFLAGMASK);
}

/*
 * 为给定级别构建前缀树。
 *
 * Conf：当前字典。
 * low：Conf->Spell数组的下边界索引。
 * high：Conf->Spell数组的上边界索引。
 * level：当前前缀树级别。
 */
static SPNode *
fc_mkSPNode(IspellDict *fc_Conf, int fc_low, int fc_high, int fc_level)
{
	int			fc_i;
	int			fc_nchar = 0;
	char		fc_lastchar = '\0';
	SPNode	   *fc_rs;
	SPNodeData *fc_data;
	int			fc_lownew = fc_low;

	for (fc_i = fc_low; fc_i < fc_high; fc_i++)
		if (fc_Conf->Spell[fc_i]->p.d.len > fc_level && fc_lastchar != fc_Conf->Spell[fc_i]->word[fc_level])
		{
			fc_nchar++;
			fc_lastchar = fc_Conf->Spell[fc_i]->word[fc_level];
		}

	if (!fc_nchar)
		return NULL;

	fc_rs = (SPNode *) cpalloc0(SPNHDRSZ + fc_nchar * sizeof(SPNodeData));
	fc_rs->length = fc_nchar;
	fc_data = fc_rs->data;

	fc_lastchar = '\0';
	for (fc_i = fc_low; fc_i < fc_high; fc_i++)
		if (fc_Conf->Spell[fc_i]->p.d.len > fc_level)
		{
			if (fc_lastchar != fc_Conf->Spell[fc_i]->word[fc_level])
			{
				if (fc_lastchar)
				{
					/* 前缀树的下一层级 */
					fc_data->node = fc_mkSPNode(fc_Conf, fc_lownew, fc_i, fc_level + 1);
					fc_lownew = fc_i;
					fc_data++;
				}
				fc_lastchar = fc_Conf->Spell[fc_i]->word[fc_level];
			}
			fc_data->val = ((uint8 *) (fc_Conf->Spell[fc_i]->word))[fc_level];
			if (fc_Conf->Spell[fc_i]->p.d.len == fc_level + 1)
			{
				bool		fc_clearCompoundOnly = false;

				if (fc_data->isword && fc_data->affix != fc_Conf->Spell[fc_i]->p.d.affix)
				{
					/*
					 * MergeAffix被调用了几次。如果其中一个单词
					 * 允许在复合词中出现而另一个则不允许，则
					 * 清除FF_COMPOUNDONLY标志。
					 */

					fc_clearCompoundOnly = (FF_COMPOUNDONLY & fc_data->compoundflag
										 & fc_makeCompoundFlags(fc_Conf, fc_Conf->Spell[fc_i]->p.d.affix))
						? false : true;
					fc_data->affix = fc_MergeAffix(fc_Conf, fc_data->affix, fc_Conf->Spell[fc_i]->p.d.affix);
				}
				else
					fc_data->affix = fc_Conf->Spell[fc_i]->p.d.affix;
				fc_data->isword = 1;

				fc_data->compoundflag = fc_makeCompoundFlags(fc_Conf, fc_data->affix);

				if ((fc_data->compoundflag & FF_COMPOUNDONLY) &&
					(fc_data->compoundflag & FF_COMPOUNDFLAG) == 0)
					fc_data->compoundflag |= FF_COMPOUNDFLAG;

				if (fc_clearCompoundOnly)
					fc_data->compoundflag &= ~FF_COMPOUNDONLY;
			}
		}

	/* 前缀树的下一层级 */
	fc_data->node = fc_mkSPNode(fc_Conf, fc_lownew, fc_high, fc_level + 1);

	return fc_rs;
}

/*
 * 从导入的字典和附加构建Conf->Dictionary树和AffixData。
 */
void NISortDictionary(IspellDict *fc_Conf)
{
	int			fc_i;
	int			fc_naffix;
	int			fc_curaffix;

	/* 压缩附加 */

	/*
	 * 如果我们使用标志别名，则需要使用NIImportOOAffixes()中填写的
	 * Conf->AffixData。
	 */
	if (fc_Conf->useFlagAliases)
	{
		for (fc_i = 0; fc_i < fc_Conf->nspell; fc_i++)
		{
			char	   *fc_end;

			if (*fc_Conf->Spell[fc_i]->p.flag != '\0')
			{
				fc_curaffix = strtol(fc_Conf->Spell[fc_i]->p.flag, &fc_end, 10);
				if (fc_Conf->Spell[fc_i]->p.flag == fc_end || errno == ERANGE)
					ereport(ERROR,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid affix alias \"%s\"",
									fc_Conf->Spell[fc_i]->p.flag)));
				if (fc_curaffix < 0 || fc_curaffix >= fc_Conf->nAffixData)
					ereport(ERROR,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid affix alias \"%s\"",
									fc_Conf->Spell[fc_i]->p.flag)));
				if (*fc_end != '\0' && !t_isdigit(fc_end) && !t_isspace(fc_end))
					ereport(ERROR,
							(errcode(ERRCODE_CONFIG_FILE_ERROR),
							 errmsg("invalid affix alias \"%s\"",
									fc_Conf->Spell[fc_i]->p.flag)));
			}
			else
			{
				/*
				 * 如果Conf->Spell[i]->p.flag为空，则获取Conf->AffixData的空值（0索引）。
				 */
				fc_curaffix = 0;
			}

			fc_Conf->Spell[fc_i]->p.d.affix = fc_curaffix;
			fc_Conf->Spell[fc_i]->p.d.len = strlen(fc_Conf->Spell[fc_i]->word);
		}
	}
	/* 否则在这里填充Conf->AffixData */
	else
	{
		/* 计算字典中使用的不同标志的数量 */
		qsort((void *) fc_Conf->Spell, fc_Conf->nspell, sizeof(SPELL *),
			  fc_cmpspellaffix);

		fc_naffix = 0;
		for (fc_i = 0; fc_i < fc_Conf->nspell; fc_i++)
		{
			if (fc_i == 0 ||
				strcmp(fc_Conf->Spell[fc_i]->p.flag, fc_Conf->Spell[fc_i - 1]->p.flag) != 0)
				fc_naffix++;
		}

		/*
		 * 用字典中使用的附加填充Conf->AffixData。用
		 * Conf->AffixData数组的索引替换Conf->Spell条目的文本标志字段。
		 */
		fc_Conf->AffixData = (char **) palloc0(fc_naffix * sizeof(char *));

		fc_curaffix = -1;
		for (fc_i = 0; fc_i < fc_Conf->nspell; fc_i++)
		{
			if (fc_i == 0 ||
				strcmp(fc_Conf->Spell[fc_i]->p.flag, fc_Conf->AffixData[fc_curaffix]) != 0)
			{
				fc_curaffix++;
				Assert(fc_curaffix < fc_naffix);
				fc_Conf->AffixData[fc_curaffix] = fc_cpstrdup(fc_Conf,
													 fc_Conf->Spell[fc_i]->p.flag);
			}

			fc_Conf->Spell[fc_i]->p.d.affix = fc_curaffix;
			fc_Conf->Spell[fc_i]->p.d.len = strlen(fc_Conf->Spell[fc_i]->word);
		}

		fc_Conf->lenAffixData = fc_Conf->nAffixData = fc_naffix;
	}

	/* 开始构建前缀树 */
	qsort((void *) fc_Conf->Spell, fc_Conf->nspell, sizeof(SPELL *), fc_cmpspell);
	fc_Conf->Dictionary = fc_mkSPNode(fc_Conf, 0, fc_Conf->nspell, 0);
}

/*
 * 为给定级别使用附加规则的替换字符串构建前缀树。
 * 替换字符串为空的附加不包括在前缀树中。
 * 这些附加由mkVoidAffix()包含。
 *
 * Conf：当前字典。
 * low：Conf->Affix数组的下边界索引。
 * high：Conf->Affix数组的上边界索引。
 * level：当前前缀树级别。
 * type：FF_SUFFIX或FF_PREFIX。
 */
static AffixNode *
fc_mkANode(IspellDict *fc_Conf, int fc_low, int fc_high, int fc_level, int fc_type)
{
	int			fc_i;
	int			fc_nchar = 0;
	uint8		fc_lastchar = '\0';
	AffixNode  *fc_rs;
	AffixNodeData *fc_data;
	int			fc_lownew = fc_low;
	int			fc_naff;
	AFFIX	  **fc_aff;

	for (fc_i = fc_low; fc_i < fc_high; fc_i++)
		if (fc_Conf->Affix[fc_i].replen > fc_level && fc_lastchar != GETCHAR(fc_Conf->Affix + fc_i, fc_level, fc_type))
		{
			fc_nchar++;
			fc_lastchar = GETCHAR(fc_Conf->Affix + fc_i, fc_level, fc_type);
		}

	if (!fc_nchar)
		return NULL;

	fc_aff = (AFFIX **) tmpalloc(sizeof(AFFIX *) * (fc_high - fc_low + 1));
	fc_naff = 0;

	fc_rs = (AffixNode *) cpalloc0(ANHRDSZ + fc_nchar * sizeof(AffixNodeData));
	fc_rs->length = fc_nchar;
	fc_data = fc_rs->data;

	fc_lastchar = '\0';
	for (fc_i = fc_low; fc_i < fc_high; fc_i++)
		if (fc_Conf->Affix[fc_i].replen > fc_level)
		{
			if (fc_lastchar != GETCHAR(fc_Conf->Affix + fc_i, fc_level, fc_type))
			{
				if (fc_lastchar)
				{
					/* 前缀树的下一层级 */
					fc_data->node = fc_mkANode(fc_Conf, fc_lownew, fc_i, fc_level + 1, fc_type);
					if (fc_naff)
					{
						fc_data->naff = fc_naff;
						fc_data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * fc_naff);
						memcpy(fc_data->aff, fc_aff, sizeof(AFFIX *) * fc_naff);
						fc_naff = 0;
					}
					fc_data++;
					fc_lownew = fc_i;
				}
				fc_lastchar = GETCHAR(fc_Conf->Affix + fc_i, fc_level, fc_type);
			}
			fc_data->val = GETCHAR(fc_Conf->Affix + fc_i, fc_level, fc_type);
			if (fc_Conf->Affix[fc_i].replen == fc_level + 1)
			{					/* 附加停止 */
				fc_aff[fc_naff++] = fc_Conf->Affix + fc_i;
			}
		}

	/* 前缀树的下一层级 */
	fc_data->node = fc_mkANode(fc_Conf, fc_lownew, fc_high, fc_level + 1, fc_type);
	if (fc_naff)
	{
		fc_data->naff = fc_naff;
		fc_data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * fc_naff);
		memcpy(fc_data->aff, fc_aff, sizeof(AFFIX *) * fc_naff);
		fc_naff = 0;
	}

	pfree(fc_aff);

	return fc_rs;
}

/*
 * 在前缀树中创建根虚节点。根虚节点是为具有空替换字符串（“repl”字段）的词缀而创建的。
 */
static void fc_mkVoidAffix(IspellDict *fc_Conf, bool fc_issuffix, int fc_startsuffix)
{
	int			fc_i,
				fc_cnt = 0;
	int			fc_start = (fc_issuffix) ? fc_startsuffix : 0;
	int			fc_end = (fc_issuffix) ? fc_Conf->naffixes : fc_startsuffix;
	AffixNode  *fc_Affix = (AffixNode *) palloc0(ANHRDSZ + sizeof(AffixNodeData));

	fc_Affix->length = 1;
	fc_Affix->isvoid = 1;

	if (fc_issuffix)
	{
		fc_Affix->data->node = fc_Conf->Suffix;
		fc_Conf->Suffix = fc_Affix;
	}
	else
	{
		fc_Affix->data->node = fc_Conf->Prefix;
		fc_Conf->Prefix = fc_Affix;
	}

	/* 计算具有空替换字符串的词缀 */
	for (fc_i = fc_start; fc_i < fc_end; fc_i++)
		if (fc_Conf->Affix[fc_i].replen == 0)
			fc_cnt++;

	/* 没有具有空替换字符串的词缀 */
	if (fc_cnt == 0)
		return;

	fc_Affix->data->aff = (AFFIX **) cpalloc(sizeof(AFFIX *) * fc_cnt);
	fc_Affix->data->naff = (uint32) fc_cnt;

	fc_cnt = 0;
	for (fc_i = fc_start; fc_i < fc_end; fc_i++)
		if (fc_Conf->Affix[fc_i].replen == 0)
		{
			fc_Affix->data->aff[fc_cnt] = fc_Conf->Affix + fc_i;
			fc_cnt++;
		}
}

/*
 * 检查词缀标志是否被字典使用。如果这个标志在.dict文件中没有实际使用，
 * Conf->AffixData将不包含词缀标志。
 *
 * Conf：当前字典。
 * affixflag：词缀标志。
 *
 * 如果Conf->AffixData数组包含affixflag，则返回true，否则返回false。
 */
static bool fc_isAffixInUse(IspellDict *fc_Conf, char *fc_affixflag)
{
	int			fc_i;

	for (fc_i = 0; fc_i < fc_Conf->nAffixData; fc_i++)
		if (fc_IsAffixFlagInUse(fc_Conf, fc_i, fc_affixflag))
			return true;

	return false;
}

/*
 * 从导入的词缀构建Conf->Prefix和Conf->Suffix树。
 */
void NISortAffixes(IspellDict *fc_Conf)
{
	AFFIX	   *fc_Affix;
	size_t		fc_i;
	CMPDAffix  *fc_ptr;
	int			fc_firstsuffix = fc_Conf->naffixes;

	if (fc_Conf->naffixes == 0)
		return;

	/* 将复合词缀存储在Conf->CompoundAffix数组中 */
	if (fc_Conf->naffixes > 1)
		qsort((void *) fc_Conf->Affix, fc_Conf->naffixes, sizeof(AFFIX), fc_cmpaffix);
	fc_Conf->CompoundAffix = fc_ptr = (CMPDAffix *) palloc(sizeof(CMPDAffix) * fc_Conf->naffixes);
	fc_ptr->affix = NULL;

	for (fc_i = 0; fc_i < fc_Conf->naffixes; fc_i++)
	{
		fc_Affix = &(((AFFIX *) fc_Conf->Affix)[fc_i]);
		if (fc_Affix->type == FF_SUFFIX && fc_i < fc_firstsuffix)
			fc_firstsuffix = fc_i;

		if ((fc_Affix->flagflags & FF_COMPOUNDFLAG) && fc_Affix->replen > 0 &&
			fc_isAffixInUse(fc_Conf, fc_Affix->flag))
		{
			bool		fc_issuffix = (fc_Affix->type == FF_SUFFIX);

			if (fc_ptr == fc_Conf->CompoundAffix ||
				fc_issuffix != (fc_ptr - 1)->issuffix ||
				fc_strbncmp((const unsigned char *) (fc_ptr - 1)->affix,
						 (const unsigned char *) fc_Affix->repl,
						 (fc_ptr - 1)->len))
			{
				/* 仅保留唯一和最小的后缀 */
				fc_ptr->affix = fc_Affix->repl;
				fc_ptr->len = fc_Affix->replen;
				fc_ptr->issuffix = fc_issuffix;
				fc_ptr++;
			}
		}
	}
	fc_ptr->affix = NULL;
	fc_Conf->CompoundAffix = (CMPDAffix *) repalloc(fc_Conf->CompoundAffix, sizeof(CMPDAffix) * (fc_ptr - fc_Conf->CompoundAffix + 1));

	/* 开始构建前缀树 */
	fc_Conf->Prefix = fc_mkANode(fc_Conf, 0, fc_firstsuffix, 0, FF_PREFIX);
	fc_Conf->Suffix = fc_mkANode(fc_Conf, fc_firstsuffix, fc_Conf->naffixes, 0, FF_SUFFIX);
	fc_mkVoidAffix(fc_Conf, true, fc_firstsuffix);
	fc_mkVoidAffix(fc_Conf, false, fc_firstsuffix);
}

static AffixNodeData *
fc_FindAffixes(AffixNode *fc_node, const char *fc_word, int fc_wrdlen, int *fc_level, int fc_type)
{
	AffixNodeData *fc_StopLow,
			   *fc_StopHigh,
			   *fc_StopMiddle;
	uint8 fc_symbol;

	if (fc_node->isvoid)
	{							/* 搜索虚词缀 */
		if (fc_node->data->naff)
			return fc_node->data;
		fc_node = fc_node->data->node;
	}

	while (fc_node && *fc_level < fc_wrdlen)
	{
		fc_StopLow = fc_node->data;
		fc_StopHigh = fc_node->data + fc_node->length;
		while (fc_StopLow < fc_StopHigh)
		{
			fc_StopMiddle = fc_StopLow + ((fc_StopHigh - fc_StopLow) >> 1);
			fc_symbol = GETWCHAR(fc_word, fc_wrdlen, *fc_level, fc_type);

			if (fc_StopMiddle->val == fc_symbol)
			{
				(*fc_level)++;
				if (fc_StopMiddle->naff)
					return fc_StopMiddle;
				fc_node = fc_StopMiddle->node;
				break;
			}
			else if (fc_StopMiddle->val < fc_symbol)
				fc_StopLow = fc_StopMiddle + 1;
			else
				fc_StopHigh = fc_StopMiddle;
		}
		if (fc_StopLow >= fc_StopHigh)
			break;
	}
	return NULL;
}

static char * fc_CheckAffix(const char *fc_word, size_t fc_len, AFFIX *fc_Affix, int fc_flagflags, char *fc_newword, int *fc_baselen)
{
	/*
	 * 检查复合允许标志
	 */

	if (fc_flagflags == 0)
	{
		if (fc_Affix->flagflags & FF_COMPOUNDONLY)
			return NULL;
	}
	else if (fc_flagflags & FF_COMPOUNDBEGIN)
	{
		if (fc_Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
			return NULL;
		if ((fc_Affix->flagflags & FF_COMPOUNDBEGIN) == 0)
			if (fc_Affix->type == FF_SUFFIX)
				return NULL;
	}
	else if (fc_flagflags & FF_COMPOUNDMIDDLE)
	{
		if ((fc_Affix->flagflags & FF_COMPOUNDMIDDLE) == 0 ||
			(fc_Affix->flagflags & FF_COMPOUNDFORBIDFLAG))
			return NULL;
	}
	else if (fc_flagflags & FF_COMPOUNDLAST)
	{
		if (fc_Affix->flagflags & FF_COMPOUNDFORBIDFLAG)
			return NULL;
		if ((fc_Affix->flagflags & FF_COMPOUNDLAST) == 0)
			if (fc_Affix->type == FF_PREFIX)
				return NULL;
	}

	/*
	 * 制作词缀的替换模式
	 */
	if (fc_Affix->type == FF_SUFFIX)
	{
		strcpy(fc_newword, fc_word);
		strcpy(fc_newword + fc_len - fc_Affix->replen, fc_Affix->find);
		if (fc_baselen)			/* 存储单词未变化部分的长度 */
			*fc_baselen = fc_len - fc_Affix->replen;
	}
	else
	{
		/*
		 * 如果前缀是所有未变化部分的长度，那么整个单词
		 * 仅包含前缀和后缀，所以输出
		 */
		if (fc_baselen && *fc_baselen + strlen(fc_Affix->find) <= fc_Affix->replen)
			return NULL;
		strcpy(fc_newword, fc_Affix->find);
		strcat(fc_newword, fc_word + fc_Affix->replen);
	}

	/*
	 * 检查结果单词
	 */
	if (fc_Affix->issimple)
		return fc_newword;
	else if (fc_Affix->isregis)
	{
		if (RS_execute(&(fc_Affix->reg.regis), fc_newword))
			return fc_newword;
	}
	else
	{
		pg_wchar   *fc_data;
		size_t		fc_data_len;
		int			fc_newword_len;

		/* 将数据字符串转换为宽字符 */
		fc_newword_len = strlen(fc_newword);
		fc_data = (pg_wchar *) palloc((fc_newword_len + 1) * sizeof(pg_wchar));
		fc_data_len = pg_mb2wchar_with_len(fc_newword, fc_data, fc_newword_len);

		if (pg_regexec(&(fc_Affix->reg.pregex->regex), fc_data, fc_data_len,
					   0, NULL, 0, NULL, 0) == REG_OKAY)
		{
			pfree(fc_data);
			return fc_newword;
		}
		pfree(fc_data);
	}

	return NULL;
}

static int fc_addToResult(char **fc_forms, char **fc_cur, char *fc_word)
{
	if (fc_cur - fc_forms >= MAX_NORM - 1)
		return 0;
	if (fc_forms == fc_cur || strcmp(fc_word, *(fc_cur - 1)) != 0)
	{
		*fc_cur = pstrdup(fc_word);
		*(fc_cur + 1) = NULL;
		return 1;
	}

	return 0;
}

static char ** fc_NormalizeSubWord(IspellDict *fc_Conf, char *fc_word, int fc_flag)
{
	AffixNodeData *fc_suffix = NULL,
			   *fc_prefix = NULL;
	int			fc_slevel = 0,
				fc_plevel = 0;
	int			fc_wrdlen = strlen(fc_word),
				fc_swrdlen;
	char	  **fc_forms;
	char	  **fc_cur;
	char		fc_newword[2 * MAXNORMLEN] = "";
	char		fc_pnewword[2 * MAXNORMLEN] = "";
	AffixNode  *fc_snode = fc_Conf->Suffix,
			   *fc_pnode;
	int			fc_i,
				fc_j;

	if (fc_wrdlen > MAXNORMLEN)
		return NULL;
	fc_cur = fc_forms = (char **) palloc(MAX_NORM * sizeof(char *));
	*fc_cur = NULL;


	/* 检查单词本身是否为正常形式 */
	if (fc_FindWord(fc_Conf, fc_word, VoidString, fc_flag))
	{
		*fc_cur = pstrdup(fc_word);
		fc_cur++;
		*fc_cur = NULL;
	}

	/* 查找“word”的所有其他正常形式（仅检查前缀） */
	fc_pnode = fc_Conf->Prefix;
	fc_plevel = 0;
	while (fc_pnode)
	{
		fc_prefix = fc_FindAffixes(fc_pnode, fc_word, fc_wrdlen, &fc_plevel, FF_PREFIX);
		if (!fc_prefix)
			break;
		for (fc_j = 0; fc_j < fc_prefix->naff; fc_j++)
		{
			if (fc_CheckAffix(fc_word, fc_wrdlen, fc_prefix->aff[fc_j], fc_flag, fc_newword, NULL))
			{
				/* 前缀成功 */
				if (fc_FindWord(fc_Conf, fc_newword, fc_prefix->aff[fc_j]->flag, fc_flag))
					fc_cur += fc_addToResult(fc_forms, fc_cur, fc_newword);
			}
		}
		fc_pnode = fc_prefix->node;
	}

	/*
	 * 查找“word”的所有其他正常形式（检查后缀然后
	 * 前缀）
	 */
	while (fc_snode)
	{
		int			fc_baselen = 0;

		/* 查找可能的后缀 */
		fc_suffix = fc_FindAffixes(fc_snode, fc_word, fc_wrdlen, &fc_slevel, FF_SUFFIX);
		if (!fc_suffix)
			break;
		/* 对每个后缀检查词缀 */
		for (fc_i = 0; fc_i < fc_suffix->naff; fc_i++)
		{
			if (fc_CheckAffix(fc_word, fc_wrdlen, fc_suffix->aff[fc_i], fc_flag, fc_newword, &fc_baselen))
			{
				/* 后缀成功 */
				if (fc_FindWord(fc_Conf, fc_newword, fc_suffix->aff[fc_i]->flag, fc_flag))
					fc_cur += fc_addToResult(fc_forms, fc_cur, fc_newword);

				/* 现在我们将查看带有前缀的变化单词 */
				fc_pnode = fc_Conf->Prefix;
				fc_plevel = 0;
				fc_swrdlen = strlen(fc_newword);
				while (fc_pnode)
				{
					fc_prefix = fc_FindAffixes(fc_pnode, fc_newword, fc_swrdlen, &fc_plevel, FF_PREFIX);
					if (!fc_prefix)
						break;
					for (fc_j = 0; fc_j < fc_prefix->naff; fc_j++)
					{
						if (fc_CheckAffix(fc_newword, fc_swrdlen, fc_prefix->aff[fc_j], fc_flag, fc_pnewword, &fc_baselen))
						{
							/* 前缀成功 */
							char	   *fc_ff = (fc_prefix->aff[fc_j]->flagflags & fc_suffix->aff[fc_i]->flagflags & FF_CROSSPRODUCT) ?
							VoidString : fc_prefix->aff[fc_j]->flag;

							if (fc_FindWord(fc_Conf, fc_pnewword, fc_ff, fc_flag))
								fc_cur += fc_addToResult(fc_forms, fc_cur, fc_pnewword);
						}
					}
					fc_pnode = fc_prefix->node;
				}
			}
		}

		fc_snode = fc_suffix->node;
	}

	if (fc_cur == fc_forms)
	{
		pfree(fc_forms);
		return NULL;
	}
	return fc_forms;
}

typedef struct SplitVar
{
	int			nstem;
	int			lenstem;
	char	  **stem;
	struct SplitVar *next;
} SplitVar;

static int fc_CheckCompoundAffixes(CMPDAffix **fc_ptr, char *fc_word, int fc_len, bool fc_CheckInPlace)
{
	bool		fc_issuffix;

	/* 如果CompoundAffix为null： */
	if (*fc_ptr == NULL)
		return -1;

	if (fc_CheckInPlace)
	{
		while ((*fc_ptr)->affix)
		{
			if (fc_len > (*fc_ptr)->len && strncmp((*fc_ptr)->affix, fc_word, (*fc_ptr)->len) == 0)
			{
				fc_len = (*fc_ptr)->len;
				fc_issuffix = (*fc_ptr)->issuffix;
				(*fc_ptr)++;
				return (fc_issuffix) ? fc_len : 0;
			}
			(*fc_ptr)++;
		}
	}
	else
	{
		char	   *fc_affbegin;

		while ((*fc_ptr)->affix)
		{
			if (fc_len > (*fc_ptr)->len && (fc_affbegin = strstr(fc_word, (*fc_ptr)->affix)) != NULL)
			{
				fc_len = (*fc_ptr)->len + (fc_affbegin - fc_word);
				fc_issuffix = (*fc_ptr)->issuffix;
				(*fc_ptr)++;
				return (fc_issuffix) ? fc_len : 0;
			}
			(*fc_ptr)++;
		}
	}
	return -1;
}

static SplitVar *
fc_CopyVar(SplitVar *fc_s, int fc_makedup)
{
	SplitVar   *fc_v = (SplitVar *) palloc(sizeof(SplitVar));

	fc_v->next = NULL;
	if (fc_s)
	{
		int			fc_i;

		fc_v->lenstem = fc_s->lenstem;
		fc_v->stem = (char **) palloc(sizeof(char *) * fc_v->lenstem);
		fc_v->nstem = fc_s->nstem;
		for (fc_i = 0; fc_i < fc_s->nstem; fc_i++)
			fc_v->stem[fc_i] = (fc_makedup) ? pstrdup(fc_s->stem[fc_i]) : fc_s->stem[fc_i];
	}
	else
	{
		fc_v->lenstem = 16;
		fc_v->stem = (char **) palloc(sizeof(char *) * fc_v->lenstem);
		fc_v->nstem = 0;
	}
	return fc_v;
}

static void fc_AddStem(SplitVar *fc_v, char *fc_word)
{
	if (fc_v->nstem >= fc_v->lenstem)
	{
		fc_v->lenstem *= 2;
		fc_v->stem = (char **) repalloc(fc_v->stem, sizeof(char *) * fc_v->lenstem);
	}

	fc_v->stem[fc_v->nstem] = fc_word;
	fc_v->nstem++;
}

static SplitVar *
fc_SplitToVariants(IspellDict *fc_Conf, SPNode *fc_snode, SplitVar *fc_orig, char *fc_word, int fc_wordlen, int fc_startpos, int fc_minpos)
{
	SplitVar   *fc_var = NULL;
	SPNodeData *fc_StopLow,
			   *fc_StopHigh,
			   *fc_StopMiddle = NULL;
	SPNode	   *fc_node = (fc_snode) ? fc_snode : fc_Conf->Dictionary;
	int			fc_level = (fc_snode) ? fc_minpos : fc_startpos;	/* 递归
														 * minpos==level */
	int			fc_lenaff;
	CMPDAffix  *fc_caff;
	char	   *fc_notprobed;
	int			fc_compoundflag = 0;

	/* 由于这个函数递归，可能会导致栈溢出 */
	check_stack_depth();

	fc_notprobed = (char *) palloc(fc_wordlen);
	memset(fc_notprobed, 1, fc_wordlen);
	fc_var = fc_CopyVar(fc_orig, 1);

	while (fc_level < fc_wordlen)
	{
		/* 查找具有插入音或/和复合词缀的单词 */
		fc_caff = fc_Conf->CompoundAffix;
		while (fc_level > fc_startpos && (fc_lenaff = fc_CheckCompoundAffixes(&fc_caff, fc_word + fc_level, fc_wordlen - fc_level, (fc_node) ? true : false)) >= 0)
		{
			/*
			 * 有一个复合词缀，所以检查单词是否存在
			 */
			char		fc_buf[MAXNORMLEN];
			char	  **fc_subres;

			fc_lenaff = fc_level - fc_startpos + fc_lenaff;

			if (!fc_notprobed[fc_startpos + fc_lenaff - 1])
				continue;

			if (fc_level + fc_lenaff - 1 <= fc_minpos)
				continue;

			if (fc_lenaff >= MAXNORMLEN)
				continue;		/* 跳过过大值 */
			if (fc_lenaff > 0)
				memcpy(fc_buf, fc_word + fc_startpos, fc_lenaff);
			fc_buf[fc_lenaff] = '\0';

			if (fc_level == 0)
				fc_compoundflag = FF_COMPOUNDBEGIN;
			else if (fc_level == fc_wordlen - 1)
				fc_compoundflag = FF_COMPOUNDLAST;
			else
				fc_compoundflag = FF_COMPOUNDMIDDLE;
			fc_subres = fc_NormalizeSubWord(fc_Conf, fc_buf, fc_compoundflag);
			if (fc_subres)
			{
				/* 是的，它是字典中的一个单词 */
				SplitVar   *fc_new = fc_CopyVar(fc_var, 0);
				SplitVar   *fc_ptr = fc_var;
				char	  **fc_sptr = fc_subres;

				fc_notprobed[fc_startpos + fc_lenaff - 1] = 0;

				while (*fc_sptr)
				{
					fc_AddStem(fc_new, *fc_sptr);
					fc_sptr++;
				}
				pfree(fc_subres);

				while (fc_ptr->next)
					fc_ptr = fc_ptr->next;
				fc_ptr->next = fc_SplitToVariants(fc_Conf, NULL, fc_new, fc_word, fc_wordlen, fc_startpos + fc_lenaff, fc_startpos + fc_lenaff);

				pfree(fc_new->stem);
				pfree(fc_new);
			}
		}

		if (!fc_node)
			break;

		fc_StopLow = fc_node->data;
		fc_StopHigh = fc_node->data + fc_node->length;
		while (fc_StopLow < fc_StopHigh)
		{
			fc_StopMiddle = fc_StopLow + ((fc_StopHigh - fc_StopLow) >> 1);
			if (fc_StopMiddle->val == ((uint8 *) (fc_word))[fc_level])
				break;
			else if (fc_StopMiddle->val < ((uint8 *) (fc_word))[fc_level])
				fc_StopLow = fc_StopMiddle + 1;
			else
				fc_StopHigh = fc_StopMiddle;
		}

		if (fc_StopLow < fc_StopHigh)
		{
			if (fc_startpos == 0)
				fc_compoundflag = FF_COMPOUNDBEGIN;
			else if (fc_level == fc_wordlen - 1)
				fc_compoundflag = FF_COMPOUNDLAST;
			else
				fc_compoundflag = FF_COMPOUNDMIDDLE;

			/* 查找不定式 */
			if (fc_StopMiddle->isword &&
				(fc_StopMiddle->compoundflag & fc_compoundflag) &&
				fc_notprobed[fc_level])
			{
				/* 好的，我们找到了完整的复合允许单词 */
				if (fc_level > fc_minpos)
				{
					/* 并且它的长度超过最小值 */
					if (fc_wordlen == fc_level + 1)
					{
						/* 好吧，这已经是最后一个单词了 */
						fc_AddStem(fc_var, pnstrdup(fc_word + fc_startpos, fc_wordlen - fc_startpos));
						pfree(fc_notprobed);
						return fc_var;
					}
					else
					{
						/* 然后我们将在同一个点查找更大的单词 */
						SplitVar   *fc_ptr = fc_var;

						while (fc_ptr->next)
							fc_ptr = fc_ptr->next;
						fc_ptr->next = fc_SplitToVariants(fc_Conf, fc_node, fc_var, fc_word, fc_wordlen, fc_startpos, fc_level);
						/* 我们可以找到下一个单词 */
						fc_level++;
						fc_AddStem(fc_var, pnstrdup(fc_word + fc_startpos, fc_level - fc_startpos));
						fc_node = fc_Conf->Dictionary;
						fc_startpos = fc_level;
						continue;
					}
				}
			}
			fc_node = fc_StopMiddle->node;
		}
		else
			fc_node = NULL;
		fc_level++;
	}

	fc_AddStem(fc_var, pnstrdup(fc_word + fc_startpos, fc_wordlen - fc_startpos));
	pfree(fc_notprobed);
	return fc_var;
}

static void fc_addNorm(TSLexeme **fc_lres, TSLexeme **fc_lcur, char *fc_word, int fc_flags, uint16 fc_NVariant)
{
	if (*fc_lres == NULL)
		*fc_lcur = *fc_lres = (TSLexeme *) palloc(MAX_NORM * sizeof(TSLexeme));

	if (*fc_lcur - *fc_lres < MAX_NORM - 1)
	{
		(*fc_lcur)->lexeme = fc_word;
		(*fc_lcur)->flags = fc_flags;
		(*fc_lcur)->nvariant = fc_NVariant;
		(*fc_lcur)++;
		(*fc_lcur)->lexeme = NULL;
	}
}

TSLexeme *
NINormalizeWord(IspellDict *fc_Conf, char *fc_word)
{
	char	  **fc_res;
	TSLexeme   *fc_lcur = NULL,
			   *fc_lres = NULL;
	uint16		fc_NVariant = 1;

	fc_res = fc_NormalizeSubWord(fc_Conf, fc_word, 0);

	if (fc_res)
	{
		char	  **fc_ptr = fc_res;

		while (*fc_ptr && (fc_lcur - fc_lres) < MAX_NORM)
		{
			fc_addNorm(&fc_lres, &fc_lcur, *fc_ptr, 0, fc_NVariant++);
			fc_ptr++;
		}
		pfree(fc_res);
	}

	if (fc_Conf->usecompound)
	{
		int			fc_wordlen = strlen(fc_word);
		SplitVar   *fc_ptr,
				   *fc_var = fc_SplitToVariants(fc_Conf, NULL, NULL, fc_word, fc_wordlen, 0, -1);
		int			fc_i;

		while (fc_var)
		{
			if (fc_var->nstem > 1)
			{
				char	  **fc_subres = fc_NormalizeSubWord(fc_Conf, fc_var->stem[fc_var->nstem - 1], FF_COMPOUNDLAST);

				if (fc_subres)
				{
					char	  **fc_subptr = fc_subres;

					while (*fc_subptr)
					{
						for (fc_i = 0; fc_i < fc_var->nstem - 1; fc_i++)
						{
							fc_addNorm(&fc_lres, &fc_lcur, (fc_subptr == fc_subres) ? fc_var->stem[fc_i] : pstrdup(fc_var->stem[fc_i]), 0, fc_NVariant);
						}

						fc_addNorm(&fc_lres, &fc_lcur, *fc_subptr, 0, fc_NVariant);
						fc_subptr++;
						fc_NVariant++;
					}

					pfree(fc_subres);
					fc_var->stem[0] = NULL;
					pfree(fc_var->stem[fc_var->nstem - 1]);
				}
			}

			for (fc_i = 0; fc_i < fc_var->nstem && fc_var->stem[fc_i]; fc_i++)
				pfree(fc_var->stem[fc_i]);
			fc_ptr = fc_var->next;
			pfree(fc_var->stem);
			pfree(fc_var);
			fc_var = fc_ptr;
		}
	}

	return fc_lres;
}
